package cn.jiedanba.cacert.common.ca.extension;

import java.io.IOException;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERGeneralizedTime;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
import org.bouncycastle.asn1.misc.NetscapeCertType;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.CertPolicyId;
import org.bouncycastle.asn1.x509.CertificatePolicies;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.asn1.x509.PolicyMappings;
import org.bouncycastle.asn1.x509.PolicyQualifierInfo;
import org.bouncycastle.asn1.x509.PrivateKeyUsagePeriod;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509ExtensionUtils;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;

import cn.jiedanba.cacert.common.bc.base.BaseSecurity;
import cn.jiedanba.cacert.common.ca.extension.domain.PkiCertPolicy;

public class ExtensionUtil extends BaseSecurity {

	/**
	 * 基本约束
	 *
	 * @return 基本约束 Extension
	 */
	public static Extension getBasicConstraintsExtension(boolean isCA) throws IOException {

		Extension extension = new Extension(Extension.basicConstraints, true,
				new BasicConstraints(isCA).toASN1Primitive().getEncoded());
		return extension;
	}

	/**
	 * 备选使用者名称
	 *
	 * @return 备选使用者名称
	 */
	public static Extension getSubjectAlternativeNameExtension(TreeSet<String> dnsNames, TreeSet<String> ipAddress)
			throws IOException {
		List<GeneralName> list = new ArrayList<>();
		if (!ObjectUtils.isEmpty(dnsNames)) {
			for (Iterator<String> iter = dnsNames.iterator(); iter.hasNext();) {
				list.add(new GeneralName(GeneralName.dNSName, iter.next()));
			}
		}
		if (!ObjectUtils.isEmpty(ipAddress)) {
			for (Iterator<String> iter = ipAddress.iterator(); iter.hasNext();) {
				list.add(new GeneralName(GeneralName.iPAddress, iter.next()));
			}
		}

		GeneralName[] generalNames = list.toArray(new GeneralName[list.size()]);
		GeneralNames subjectAltNames = new GeneralNames(generalNames);
		Extension extension = new Extension(Extension.subjectAlternativeName, false,
				subjectAltNames.toASN1Primitive().getEncoded());
		return extension;
	}

	/**
	 * 全部密钥用法 // 如果使用固定的自己精简
	 *
	 * @return All Key Usage
	 */
	public static KeyUsage getAllKeyUsage() {
		int usage = X509KeyUsage.digitalSignature | X509KeyUsage.nonRepudiation | X509KeyUsage.keyEncipherment
				| X509KeyUsage.dataEncipherment | X509KeyUsage.keyAgreement | X509KeyUsage.keyCertSign
				| X509KeyUsage.cRLSign | X509KeyUsage.encipherOnly | X509KeyUsage.decipherOnly;
		KeyUsage keyUsage = new KeyUsage(usage);

		return keyUsage;
	}

	/**
	 * 获取扩展密钥用途
	 *
	 * @return 增强密钥用法ASN.1对象
	 * @author
	 * @dates
	 */
	public static Extension getKeyUsageExtension() {
		KeyPurposeId[] keyPurposeIds = new KeyPurposeId[2];
		keyPurposeIds[0] = KeyPurposeId.id_kp_serverAuth;
		keyPurposeIds[1] = KeyPurposeId.id_kp_clientAuth;
		ExtendedKeyUsage extendedKeyUsage = new ExtendedKeyUsage(keyPurposeIds);
		Extension extension = null;
		try {
			extension = new Extension(Extension.extendedKeyUsage, false,
					extendedKeyUsage.toASN1Primitive().getEncoded());
		} catch (IOException e) {
			e.printStackTrace();
		}

		return extension;
	}

	/**
	 * 使用者密钥标识
	 *
	 * @param publicKey
	 *            使用者公钥
	 * @return 使用者密钥标识
	 */
	public static Extension getSubjectKeyIdentifierExtension(PublicKey publicKey)
			throws OperatorCreationException, IOException {
		SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo
				.getInstance(ASN1Sequence.getInstance(publicKey.getEncoded()));
		DigestCalculator calculator = new BcDigestCalculatorProvider()
				.get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1));
		X509ExtensionUtils extensionUtils = new X509ExtensionUtils(calculator);
		Extension extension = new Extension(Extension.subjectKeyIdentifier, false,
				extensionUtils.createSubjectKeyIdentifier(subjectPublicKeyInfo).toASN1Primitive().getEncoded());
		return extension;
	}

	/**
	 * 颁发者密钥标识
	 *
	 * @param publicKey
	 *            颁发者公钥
	 * @return 颁发者密钥标识
	 */
	public static Extension getAuthorityKeyIdentifierExtension(PublicKey publicKey)
			throws OperatorCreationException, IOException {
		SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo
				.getInstance(ASN1Sequence.getInstance(publicKey.getEncoded()));
		DigestCalculator calculator = new BcDigestCalculatorProvider()
				.get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1));
		X509ExtensionUtils extensionUtils = new X509ExtensionUtils(calculator);
		Extension extension = new Extension(Extension.authorityKeyIdentifier, false,
				extensionUtils.createAuthorityKeyIdentifier(subjectPublicKeyInfo).toASN1Primitive().getEncoded());
		return extension;
	}

	/**
	 * 添加CRL分布点
	 * 
	 * @param names
	 * @return
	 * @throws IOException
	 */
	public static Extension getCRLDIstPointExtension(String[] names) throws IOException {
		DistributionPoint[] distributionPoints = new DistributionPoint[names.length];
		int index = 0;
		for (String name : names) {
			DistributionPointName distributionPoint = new DistributionPointName(
					new GeneralNames(new GeneralName(GeneralName.uniformResourceIdentifier, name)));
			distributionPoints[index] = new DistributionPoint(distributionPoint, null, null);
			index++;
		}
		CRLDistPoint crlDistPoint = new CRLDistPoint(distributionPoints);
		Extension extension = new Extension(Extension.cRLDistributionPoints, false,
				crlDistPoint.toASN1Primitive().getEncoded());
		return extension;
	}

	/**
	 * 2.5.29.32.0 所有颁发策略<br/>
	 * 证书策略映射
	 *
	 * @return Policies 证书策略映射
	 */
	public static Extension getPolicyMappingsExtension() throws IOException {

		// Test Data
		CertPolicyId iCertPolicyId = CertPolicyId.getInstance(new ASN1ObjectIdentifier("2.16.840.1.114412.1.2"));
		CertPolicyId sCertPolicyId = CertPolicyId.getInstance(new ASN1ObjectIdentifier("2.23.140.1.2.1"));

		CertPolicyId[] issuerCertPolicyIds = new CertPolicyId[2];
		CertPolicyId[] subjectCertPolicyIds = new CertPolicyId[2];

		issuerCertPolicyIds[0] = iCertPolicyId;
		subjectCertPolicyIds[0] = sCertPolicyId;
		issuerCertPolicyIds[1] = iCertPolicyId;
		subjectCertPolicyIds[1] = sCertPolicyId;

		// 单组
		// PolicyMappings policyMappings = new PolicyMappings(iCertPolicyId,
		// sCertPolicyId);
		// 多组
		PolicyMappings policyMappings = new PolicyMappings(issuerCertPolicyIds, subjectCertPolicyIds);

		Extension extension = new Extension(Extension.policyMappings, false,
				policyMappings.toASN1Primitive().getEncoded());

		return extension;
	}

	/**
	 * 证书策略
	 *
	 * @return Policies 证书策略
	 */
	public static Extension getCertificatePoliciesExtension() throws IOException {

		PolicyInformation[] policyInformations = new PolicyInformation[1];
		PolicyQualifierInfo policyQualifierInfo = new PolicyQualifierInfo("http://47.98.124.142:8085/cps");
		policyInformations[0] = new PolicyInformation(new ASN1ObjectIdentifier("2.16.840.1.114412.1.1"),
				new DERSequence(policyQualifierInfo));

		CertificatePolicies certificatePolicies = new CertificatePolicies(policyInformations);

		Extension extension = new Extension(Extension.certificatePolicies, false,
				certificatePolicies.toASN1Primitive().getEncoded());

		return extension;
	}

	/**
	 * 证书策略
	 *
	 * @return Policies 证书策略
	 */
	public static Extension getCertificatePoliciesExtension(String cps, String oid) throws IOException {

		PolicyInformation[] policyInformations = new PolicyInformation[1];
		PolicyQualifierInfo policyQualifierInfo = new PolicyQualifierInfo(cps);
		policyInformations[0] = new PolicyInformation(new ASN1ObjectIdentifier(oid),
				new DERSequence(policyQualifierInfo));

		CertificatePolicies certificatePolicies = new CertificatePolicies(policyInformations);

		Extension extension = new Extension(Extension.certificatePolicies, false,
				certificatePolicies.toASN1Primitive().getEncoded());

		return extension;
	}

	/**
	 * 证书策略
	 *
	 * @return Policies 证书策略
	 */
	public static Extension getCertificatePoliciesExtension(List<PkiCertPolicy> policys) throws IOException {

		int policysSize = policys.size();
		List<PolicyInformation> policyInformations = new ArrayList<>();
		for (int i = 0; i < policysSize; i++) {
			PkiCertPolicy ent = policys.get(i);
			if (StringUtils.isNotBlank(ent.getPolicyValue()) && StringUtils.isNotBlank(ent.getPolicyOid())) {
				PolicyQualifierInfo policyQualifierInfo = new PolicyQualifierInfo(ent.getPolicyValue());
				PolicyInformation policyInformation = new PolicyInformation(
						new ASN1ObjectIdentifier(ent.getPolicyOid()), new DERSequence(policyQualifierInfo));
				policyInformations.add(policyInformation);
			} else {
				PolicyInformation policyInformation = new PolicyInformation(
						new ASN1ObjectIdentifier(ent.getPolicyOid()));
				policyInformations.add(policyInformation);
			}

		}
		CertificatePolicies certificatePolicies = new CertificatePolicies(
				policyInformations.toArray(new PolicyInformation[policyInformations.size()]));
		Extension extension = new Extension(Extension.certificatePolicies, false,
				certificatePolicies.toASN1Primitive().getEncoded());

		return extension;
	}

	/***
	 * 权限信息访问 OCSP 和crt
	 *
	 * @return Extension 权限信息访问
	 */
	public static Extension getAuthorityInfoAccessExtensionOCSPCRL(String ocspUrl, String crtUrl) throws IOException {
		ASN1EncodableVector authorityInnfoAccess = new ASN1EncodableVector();
		if (StringUtils.isNotBlank(ocspUrl)) {
			AccessDescription ocsp = new AccessDescription(AccessDescription.id_ad_ocsp,
					new GeneralName(GeneralName.uniformResourceIdentifier, ocspUrl));
			authorityInnfoAccess.add(ocsp);
		}
		if (StringUtils.isNotBlank(crtUrl)) {
			AccessDescription caIssuers = new AccessDescription(AccessDescription.id_ad_caIssuers,
					new GeneralName(GeneralName.uniformResourceIdentifier, crtUrl));
			authorityInnfoAccess.add(caIssuers);
		}
		if (authorityInnfoAccess.size() > 0) {
			Extension extension = new Extension(Extension.authorityInfoAccess, false,
					new DERSequence(authorityInnfoAccess).getEncoded());
			return extension;
		}
		return null;

	}

	/**
	 * 自定义扩展
	 * 
	 * @param oid
	 * @param value
	 *            值
	 * @return
	 * @throws IOException
	 */
	public static Extension getExtendAttributes(String oid, String value) throws IOException {
		ASN1ObjectIdentifier attributesOid = new ASN1ObjectIdentifier(oid).intern();
		Extension extension = new Extension(attributesOid, false, new DERIA5String(value).getEncoded());
		return extension;
	}

	public static Extension getNetscapeCertType() throws IOException {
		Extension extension = new Extension(MiscObjectIdentifiers.netscapeCertType, false,
				new NetscapeCertType(
						NetscapeCertType.sslCA | NetscapeCertType.smimeCA | NetscapeCertType.objectSigningCA)
								.getEncoded());
		return extension;
	}

	/**
	 * 私钥使用周期
	 * 
	 * @return
	 * @throws IOException
	 */
	public static Extension getPrivateKeyUsagePeriod(Date beginDate, Date endDate) throws IOException {
		ASN1EncodableVector v = new ASN1EncodableVector();
		DERGeneralizedTime fromTime = new DERGeneralizedTime(beginDate);
		v.add(new DERTaggedObject(false, 0, fromTime));
		DERGeneralizedTime toTime = new DERGeneralizedTime(endDate);
		v.add(new DERTaggedObject(false, 1, toTime));
		DERSequence o = new DERSequence(v);
		PrivateKeyUsagePeriod pkup = PrivateKeyUsagePeriod.getInstance(o);

		Extension extension = new Extension(Extension.privateKeyUsagePeriod, false, pkup.getEncoded());
		return extension;
	}

}
